package ms.common.util.bean;

import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class BeanUtil {

	private static final Map<String, BeanCopier> beanIfaceMap = new HashMap<>();

	private static final ReadWriteLock rwl = new ReentrantReadWriteLock();

	//    private static Log logger = LogFactory.getLog(BeanUtil.class);

	private static boolean debug = false;

	private static boolean enableSynchronized = false;

	public static void clearAll() {
		beanIfaceMap.clear();
	}

	public static boolean isDebug() {
		return debug;
	}

	public static void setDebug(boolean debug) {
		BeanUtil.debug = debug;
	}

	//    public static boolean isEnableSynchronized() {
	//        return enableSynchronized;
	//    }

	public static void setEnableSynchronized(boolean enableSynchronized) {
		BeanUtil.enableSynchronized = enableSynchronized;
	}

	/**
	 * 对象复制.复制对象的原始类型 byte short int long boolean double float 支持List的原始类型复制
	 * Map key value 原始类型复制.支持int和int枚举的双向复制
	 *
	 * @param from 被复制的对象
	 * @param to   目的对象
	 */
	public static void copy(Object from, Object to) {
		copy(from, to, false);
	}

	/**
	 * @param from       被复制的对象
	 * @param to         目的对象
	 * @param checkIsSet 检查是否使用了setter设置过数据，检查方式为调用
	 *                   public boolean from.isSetXXX()方法。
	 *                   如果checkSetter=true时，from必须支持isSet方法
	 */
	public static void copy(Object from, Object to, boolean checkIsSet) {
		BeanCopier beanCopier = getBeanCopier(from.getClass(), to.getClass(), checkIsSet);
		beanCopier.copy(from, to);
	}

	/**
	 * 对象复制.复制对象的原始类型 byte short int long boolean double float 支持List的原始类型复制
	 * Map key value 原始类型复制.支持int和int枚举的双向复制
	 *
	 * @param from    被复制的对象
	 * @param toClazz 目的对象类型
	 * @param <T>     被复制的对象泛型
	 * @param <E>     目的对象泛型
	 * @return 复制后的新对象
	 */
	public static <T, E> E copy(T from, Class<E> toClazz) {
		return copy(from, toClazz, false);
	}

	/**
	 * 对象复制.复制对象的原始类型 byte short int long boolean double float 支持List的原始类型复制
	 * Map key value 原始类型复制.支持int和int枚举的双向复制
	 *
	 * @param from       被复制的对象
	 * @param toClazz    目的对象类型
	 * @param checkIsSet 检查是否使用了setter设置过数据，检查方式为调用
	 *                   public boolean from.isSetXXX()方法。
	 *                   如果checkSetter=true时，from必须支持isSet方
	 * @param <T>        被复制的对象泛型
	 * @param <E>        目的对象泛型
	 * @return 复制后的新对象
	 */
	public static <T, E> E copy(T from, Class<E> toClazz, boolean checkIsSet) {
		BeanCopier beanCopier = getBeanCopier(from.getClass(), toClazz, checkIsSet);
		try {
			E e = toClazz.getConstructor().newInstance();
			beanCopier.copy(from, e);
			return e;
		} catch (Exception e1) {
			throw new RuntimeException(e1);
		}
	}

	/**
	 * 集合复制
	 *
	 * @param fromList 被复制的对象集合
	 * @param toClazz  复制后集合的元素类型
	 * @param <T>      被复制的对象泛型
	 * @param <E>      目的对象泛型
	 * @return 复制的集合
	 */
	public static <T, E> List<E> copyList(List<T> fromList, Class<E> toClazz) {
		List<E> list = new ArrayList<>();
		for (T t : fromList) {
			list.add(BeanUtil.copy(t, toClazz));
		}
		return list;
	}

	private static <T, E> BeanCopier createBeanIfaceImpl(Class<T> fromClazz, Class<E> toClazz, boolean checkIsSet) {
		//        if (debug) {
		//            logger.info("create fromClazz[" + fromClazz.getName() + "] " +
		//                    "toClazz[" + toClazz.getName() + "]");
		//        }
		JavassistBeanCopierClassCreater creater = new JavassistBeanCopierClassCreater(fromClazz, toClazz, checkIsSet);
		Class<BeanCopier> clazz = creater.getMapperClass();
		try {
			return clazz.getConstructor().newInstance();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static BeanCopier getBeanCopier(Class<?> fromClazz, Class<?> toClazz, boolean checkIsSet) {
		if (enableSynchronized) {
			return _getBeanCopier(fromClazz, toClazz, checkIsSet);
		}
		return _getBeanCopier2(fromClazz, toClazz, checkIsSet);
	}

	public static BeanCopier _getBeanCopier2(Class<?> fromClazz, Class<?> toClazz, boolean checkIsSet) {
		rwl.readLock().lock();
		String key = createKey(fromClazz, toClazz, checkIsSet);
		BeanCopier beanCopier = beanIfaceMap.get(key);
		if (beanCopier != null) {
			rwl.readLock().unlock();
			return beanCopier;
		}
		rwl.readLock().unlock();
		rwl.writeLock().lock();
		try {
			beanCopier = beanIfaceMap.get(key);
			if (beanCopier != null) {
				return beanCopier;
			}
			beanCopier = createBeanIfaceImpl(fromClazz, toClazz, checkIsSet);
			beanIfaceMap.put(key, beanCopier);
			return beanCopier;
		} finally {
			rwl.writeLock().unlock();
		}
	}

	public synchronized static BeanCopier _getBeanCopier(Class<?> fromClazz, Class<?> toClazz, boolean checkIsSet) {
		String key = createKey(fromClazz, toClazz, checkIsSet);
		BeanCopier beanCopier = beanIfaceMap.get(key);
		if (beanCopier == null) {
			beanCopier = createBeanIfaceImpl(fromClazz, toClazz, checkIsSet);
			beanIfaceMap.put(key, beanCopier);
		}
		return beanCopier;
	}

	private static String createKey(Class<?> fromClazz, Class<?> toClazz, boolean checkIsSet) {
		StringBuilder sb = new StringBuilder();
		sb.append(fromClazz.getName());
		sb.append('_');
		sb.append(toClazz.getName());
		if (checkIsSet) {
			sb.append("_1");
		}
		return sb.toString();
	}

	/**
	 * 把map数据复制到dest对象中, map中key与dest对象的属性对应,目前只支持String byte short int long double float 以及对应的对象类型的数据复制。方法通过反射实现,性能要求不高的地方可以使用
	 *
	 * @param map  原始数据map
	 * @param dest 目标对象
	 */
	public static void map2Object(Map<String, Object> map, Object dest) {
		if (dest instanceof Map) {
			Map<String, Object> _map = (Map<String, Object>) dest;
			_map.putAll(map);
		} else {
			Set<Map.Entry<String, Object>> entrySet = map.entrySet();
			for (Map.Entry<String, Object> entry : entrySet) {
				String name = entry.getKey();
				Object value = entry.getValue();
				setValue(dest.getClass(), dest, name, value);
			}
		}
	}

	private static void setValue(Class<?> clazz, Object object, String fieldName, Object value) {
		Field field = ReflectionUtils.findField(clazz, fieldName);
		if (field == null) {
			return;
		}
		field.setAccessible(true);
		Class<?> fieldType = field.getType();
		String objStr = value.toString();
		try {
			if (fieldType.equals(byte.class) || fieldType.equals(Byte.class)) {
				field.set(object, new BigDecimal(objStr).byteValue());
			} else if (fieldType.equals(short.class) || fieldType.equals(Short.class)) {
				field.set(object, new BigDecimal(objStr).shortValue());
			} else if (fieldType.equals(int.class) || fieldType.equals(Integer.class)) {
				field.set(object, new BigDecimal(objStr).intValue());
			} else if (fieldType.equals(long.class) || fieldType.equals(Long.class)) {
				field.set(object, new BigDecimal(objStr).longValue());
			} else if (fieldType.equals(double.class) || fieldType.equals(Double.class)) {
				field.set(object, new BigDecimal(objStr).doubleValue());
			} else if (fieldType.equals(float.class) || fieldType.equals(Float.class)) {
				field.set(object, new BigDecimal(objStr).floatValue());
			} else if (fieldType.equals(boolean.class) || fieldType.equals(Boolean.class)) {
				field.set(object, Boolean.parseBoolean(objStr));
			} else if (fieldType.equals(String.class)) {
				field.set(object, objStr);
			}
		} catch (IllegalAccessException e) {
		}
	}
}
